package collection;

import java.util.Iterator;

import exception.CollectionEmptyException;

public abstract class ALinkedList<N extends ANode<T>, T> implements ILinkedList<N, T>{
	
	protected N   head;
	protected N   tail;
	protected int size;
	
	public ALinkedList()
	{
		this.head = createHead();
		this.tail = this.head;
		this.size = 0;
	}
	
	public ALinkedList(ALinkedList<N,T> other)
	{
		this();
		for (N node : other)
		{
			N newNode = this.createNode(node.getElement());
			this.addLast(newNode);
		}
	}
	
	protected abstract N createHead();

	protected N getHead() 
	{
		return head;
	}

	protected void setHead(N head) 
	{
		this.head = head;
	}

	protected N getTail() 
	{
		return tail;
	}

	protected void setTail(N tail) 
	{
		this.tail = tail;
	}
	
	@Override
	public abstract N createNode(T element);
	
	@Override
	public boolean isEmpty()
	{
		return this.head == this.tail;
	}
	
	@Override
	public void clear()
	{
		this.head = createHead();
		this.tail = this.head;
		this.size = 0;
	}
	
	@Override
	public int size()
	{
		return this.size;
	}
	
	@Override
	public N getFirst()
	{
		if (isEmpty())
		{
			throw new CollectionEmptyException("Linked List");
		}
		else
		{
			return getNext(this.head);
		}
	}
	
	@Override
	public N getLast()
	{
		if (isEmpty())
		{
			throw new CollectionEmptyException("Linked List");
		}
		else
		{
			return this.tail;
		}
	}
	
	@Override
	public abstract N getPrev(N someNode);
	
	@Override
	public abstract N getNext(N someNode);
	
	@Override
	public abstract void addFirst(N newNode);
	
	@Override
	public abstract void addLast(N newNode);
	
	@Override
	public abstract N removeFirst();
	
	@Override
	public abstract N removeLast();
	
	@Override
	public abstract void reverse();
	
	@Override
	public abstract void addBefore(N someNode, N newNode);
	
	@Override
	public abstract void addAfter(N someNode, N newNode);
	
	@Override
	public abstract void remove(N node);
	
	@Override
	public boolean contains(N node)
	{
		if (isEmpty())
		{
			return false;
		}
		
		N curNode = getNext(this.head);
		while(curNode != null)
		{
			if (curNode == node)
				return true;
			curNode = getNext(curNode);
		}
		return false;
	}
	
	@Override
	public String toString()
	{
		if (isEmpty())
		{
			return "[]";
		}
		
		String retString = "[ ";
		N curNode = getNext(this.head);
		while (curNode != null && getNext(curNode) != null)
		{
			retString += curNode.toString() + ", ";
			curNode = getNext(curNode);
		}
		if (curNode != null)
		{
			retString += curNode.toString();
		}
		retString += " ]";
		
		return retString;
	}

	@Override
	public Iterator<N> iterator() 
	{
		return new LinkedListIterator(this);
	}
	
	@Override 
	public Iterator<T> elementsIterator()
	{
		return new LinkedListElementIterator(this);
	}
	
	private class LinkedListIterator implements Iterator<N>{
		
		ALinkedList<N, T> list = null;
		N curNode = null;
		
		LinkedListIterator(ALinkedList<N, T> list)
		{
			this.list = list;
			this.curNode = list.head;
		}
		
		@Override
		public boolean hasNext()
		{
			return (list.getNext(curNode) != null);
		}
		
		@Override
		public N next()
		{
			curNode = list.getNext(curNode);
			return curNode;
		}
		
		@Override
		public void remove()
		{
			throw new UnsupportedOperationException();
		}
	}
	
	private class LinkedListElementIterator implements Iterator<T>{
		
		ALinkedList<N, T> list = null;
		N curNode = null;
		
		LinkedListElementIterator(ALinkedList<N, T> list)
		{
			this.list = list;
			this.curNode = list.head;
		}
		
		@Override
		public boolean hasNext()
		{
			return (list.getNext(curNode) != null);
		}
		
		@Override
		public T next()
		{
			curNode = list.getNext(curNode);
			return curNode.getElement();
		}
		
		@Override
		public void remove()
		{
			throw new UnsupportedOperationException();
		}
	}
	
}
